/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Service implementation of Hub Name Service.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#define _GNU_SOURCE
#include <string.h>
#include "protocolDispatcher.h"
#include "protocol/fastProtocolHdr.h"
#include "protocol/hubctrlHdr.h"
#include "apnameserviceframe.h"
#include "nameservicecore.h"
#include "owndebug.h"
#include "ownutils.h"
#include <signal.h>
#include <curses.h>
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
#define GIMNETAP_NAMESERVICE_TESTAPP_VERSIONSTRING "v0.0-Alpha"
//*****************************************************************************
//*****************************************************************************

static volatile int run = 1;
static int verbose = ODINFO;
static ownThreadHandle signalHandlerThread = NULL;
static ownThreadHandle protocolDispProcessorThreadHandle = NULL;
static ownMutexHandle lock;
static ownCondHandle cond;
static WINDOW *w = NULL;
//*****************************************************************************
static uint32_t targetNodeId = 0x00010001;
static uint32_t localNodeId = 0x00000000;
static uint32_t messageSeq = 0;
static uint32_t lastReceivedMessageSeq_async = 0;
static TProtocolDisp aplink;
//*****************************************************************************
unsigned char g_replybuffer[sizeof(TFastProtocolHeader)+sizeof(TAPNameServiceFrame)];
TFastProtocolHeader *g_replyfpframe = (TFastProtocolHeader *)g_replybuffer;
TAPNameServiceFrame *g_replynsframe = (TAPNameServiceFrame *)(g_replybuffer+sizeof(TFastProtocolHeader));
//*****************************************************************************
//*****************************************************************************

static void Lock()
{
  ownMutex_Lock(lock);
}
//*****************************************************************************

static void Unlock()
{
  ownMutex_Unlock(lock);
}
//*****************************************************************************

static void notifyReply(const int id)
{
  Lock();
  lastReceivedMessageSeq_async = id;
  ownCond_Broadcast(cond);
  Unlock();
}
//*****************************************************************************

static int waitReply(const int id, const int timeout)
{
  int result = 0;
  ownTime_ms_t begin = ownTime_get_ms();
  ownTime_ms_delta_t left = timeout;

  Lock();
  while(lastReceivedMessageSeq_async < id && left > 0) {
    ownCond_Wait(cond, lock, left);
    left = ownTime_get_ms_left(timeout, begin);
  }
  
  result = (lastReceivedMessageSeq_async >= id);
  Unlock();
  
  return result;
}
//*****************************************************************************

static int protocolDispProcessorThread(void *p)
{
  TProtocolDisp *link = (TProtocolDisp *)p;
  while(run) {
    if (protocolDispProcess(link, 1000)) {
      dPrint(ODTEST,"Process returned error!");
      run = 0;
    }
  }
  return 0;
}
//*****************************************************************************

static void processFastProtocolFrame(const TFastProtocolHeader *framehdr,
                                     const unsigned char *payload,
                                     const unsigned int payloadSize,
                                     void *userPtr)
{  
  /*  dPrint(ODTEST,"FastProtocolFrame: %p, %p, %d",
      framehdr, payload, payloadSize);*/

  // Check that payload size is atleast size of the main frame
  if (payloadSize >= sizeof(TAPNameServiceFrame)) {
    const TAPNameServiceFrame *nsframe = (const TAPNameServiceFrame *)payload;
    
    if (nsframe->service_id == GIMNETAP_SERVICE_NAMESERVICE_ID && 
        nsframe->flags & KAPNameServiceFlagReply) {
      /*      dPrint(ODTEST,"Received REPLY frame with MsgID: %u",
              nsframe->identifier);*/
      if (nsframe->request == KAPNameServiceRequestRegister) {
        printw("\rRegistering to name '%s' completed. Entry expires in %u seconds (Operation was a %s)\n",
               nsframe->name,
               nsframe->age_left,
               nsframe->result==KAPNameServiceResultOK?"success!":"failure!");

      } else if (nsframe->request == KAPNameServiceRequestIdByName) {
        printw("\rQuery for IdByName '%s' resulted in NodeId 0x%08x. Entry expires in %u seconds (Operation was a %s)\n",
               nsframe->name,
               nsframe->nodeid,
               nsframe->age_left,
               nsframe->result==KAPNameServiceResultOK?"success!":"failure!");

      } else if (nsframe->request == KAPNameServiceRequestNameById) {
        printw("\rQuery for NameById '0x%08x' resulted in Name '%s'. Entry expires in %u seconds (Operation was a %s)\n",
               nsframe->nodeid,
               nsframe->name,
               nsframe->age_left,
               nsframe->result==KAPNameServiceResultOK?"success!":"failure!");

      } else if (nsframe->request == KAPNameServiceRequestUnregister) {
        printw("\rUnregister for  '%s'/'0x%08x' completed. (Operation was a %s)\n",
               nsframe->name,
               nsframe->nodeid,
               nsframe->result==KAPNameServiceResultOK?"success!":"failure!");
        
      } else {
        printw("\rUnprocessed nameservice reply with requestId = 0x%02x, resultCode = 0x%02x\n",
               nsframe->request,
               nsframe->result); 
      
      }

      // Notify about reply.
      notifyReply(nsframe->identifier);
    
      
    }
    
  } else {
    dPrint(ODWARN,"Received payload smaller than TAPNameServiceFrame! Skip.");
    
  }
}
//*****************************************************************************

static void hubctrlProtocolCallback(int protocol_number,
                                    const unsigned char *data,
                                    int size,
                                    void *userPtr)
{ 
  // This function gets called when a HubCtrlProtocol packet is received
  // from Hub.
  const THubCtrlProtocolHeader *frame = (const THubCtrlProtocolHeader *)data;
  
  if (size >= sizeof(THubCtrlProtocolHeader) &&
      size == frame->protocol_id.total_size) {
    // Protocol size match; We have enough data to process call
    if (frame->request == KHubCtrlRequestQueryLocalNodeId &&
        frame->flags & KHubCtrlFlagReply &&
        size == sizeof(THubCtrlProtocolHeader)+sizeof(THubCtrlSubHeaderNodeInfo)) {
      THubCtrlSubHeaderNodeInfo *ni = (THubCtrlSubHeaderNodeInfo *)(frame+1);
      
      printw("\rOwn address for this module is '0x%08x' (Link delay %u us)\n",
             ni->nodeid, ni->link_delay_us);
      localNodeId = ni->nodeid;
      
      // Update fp source (not really required as Hub overrides)
      g_replyfpframe->source_id = localNodeId;
    }
    
  } else {
    dPrint(ODWARN,"Warning: Received invalid HubCtrlProtocol frame!");
    
  }
}
//*****************************************************************************

static void fastProtocolCallback(int protocol_number,
                                 const unsigned char *data,
                                 int size,
                                 void *userPtr)
{
  // This function gets called when a FastProtocol packet is received
  // from Hub.
  const TFastProtocolHeader *frame = (const TFastProtocolHeader *)data;

  if (size >= sizeof(TFastProtocolHeader) &&
      size == frame->protocol_id.total_size) {
    // Protocol size match; We have enough data to process call
    processFastProtocolFrame(frame,
                             data+sizeof(TFastProtocolHeader),
                             size-sizeof(TFastProtocolHeader),
                             userPtr);

  } else {
    dPrint(ODWARN,"Warning: Received invalid FastProtocol frame!");
    
  }
}
//*****************************************************************************


static void requestOwnId(TProtocolDisp *link)
{
  THubCtrlProtocolHeader frame;

  // Clear header.
  memset(&frame, 0x00, sizeof(frame));
  
  // Assign identification information
  protocolIdentificationConstructHeader(&frame.protocol_id,
                                        HUBCTRLPROTOCOL_NUMBER,
                                        sizeof(THubCtrlProtocolHeader));
  
  // Copy parameters
  frame.request = KHubCtrlRequestQueryLocalNodeId;
  frame.flags = 0x00;
  frame.subheader_count = 0;
  frame.result = 0;
  frame.message_id = ++messageSeq;
  
  protocolDispSendPacket(link,(void*)&frame,sizeof(frame));
}
//*****************************************************************************

static void showwait()
{
  const char value[] = "|/-\\";
  static int phase = 0;
  printw("\r %c: ", value[phase]);
  phase = (phase + 1) % 4;
}
//*****************************************************************************

static void testRun1(void)
{
  unsigned char replybuffer[sizeof(TFastProtocolHeader)+sizeof(TAPNameServiceFrame)];
  TFastProtocolHeader *replyfpframe = (TFastProtocolHeader *)replybuffer;
  TAPNameServiceFrame *replynsframe = (TAPNameServiceFrame *)(replyfpframe+1);
  
  // Assign the fastprotocolframe
  memset(replyfpframe, 0x00, sizeof(TFastProtocolHeader));
  replyfpframe->source_id = 0x00;
  replyfpframe->target_id = targetNodeId;
  replyfpframe->flags = 0;
  replyfpframe->ttl = FASTPROTOCOL_DEFAULT_TTL;
  replyfpframe->statusvalue = 0x00;
  replyfpframe->protocol_id.protocol_number = FASTPROTOCOL_NUMBER;
  replyfpframe->protocol_id.total_size = sizeof(replybuffer);


  printw("TestRun 1 - against '0x%08x' - Executing...\n", targetNodeId);
  TAPNameServiceFrame reg_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestRegister,
                                    0x0000,
                                    ++messageSeq,
                                    "TestRun1",
                                    0x00000000,
                                    1000 };
  *replynsframe = reg_frame;
  protocolDispSendPacket(&aplink,(void*)&replybuffer,sizeof(replybuffer));
  if (!waitReply(messageSeq, 1000)) printw("Failed to receive reply for QueryId %u!\n",
                                           messageSeq);     
  
  TAPNameServiceFrame qibn_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestIdByName,
                                    0x0000,
                                    ++messageSeq,
                                    "TestRun1",
                                    0x0,
                                    0 };
  *replynsframe = qibn_frame;
  protocolDispSendPacket(&aplink,(void*)&replybuffer,sizeof(replybuffer));
  if (!waitReply(messageSeq, 1000)) printw("Failed to receive reply for QueryId %u!\n",
                                           messageSeq);     
  
  TAPNameServiceFrame qnbi_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestNameById,
                                    0x0000,
                                    ++messageSeq,
                                    "",
                                    localNodeId,
                                    0 };
  *replynsframe = qnbi_frame;
  protocolDispSendPacket(&aplink,(void*)&replybuffer,sizeof(replybuffer));
  if (!waitReply(messageSeq, 1000)) printw("Failed to receive reply for QueryId %u!\n",
                                           messageSeq);     

  TAPNameServiceFrame ureg_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                     KAPNameServiceRequestUnregister,
                                     0x0000,
                                     ++messageSeq,
                                     "TestRun1",
                                     localNodeId,
                                     1000 };
  *replynsframe = ureg_frame;
  protocolDispSendPacket(&aplink,(void*)&replybuffer,sizeof(replybuffer));
  if (!waitReply(messageSeq, 1000)) printw("Failed to receive reply for QueryId %u!\n",
                                           messageSeq);     
  
  printw("TestRun 1 - Completed.\n");
}
//*****************************************************************************

static void test_Register(void)
{
  static char name[33] = "default";
  int validity = 100;
  printw("Register ownid (0x%08x) to name [%s]: ", localNodeId, name);
  scanw("%s", name);
  printw("Entry validity time (s) [60]: ");
  if (scanw("%d", &validity) != 1) validity = 60;
  halfdelay(1);

  const double begin = ownTime_get_s();

  TAPNameServiceFrame reg_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestRegister,
                                    0x0000,
                                    ++messageSeq,
                                    "",
                                    0x00000000,
                                    validity };
  // Copy name from variable
  strcpy(reg_frame.name, name);
  
  *g_replynsframe = reg_frame;
  protocolDispSendPacket(&aplink,(void*)&g_replybuffer,sizeof(g_replybuffer));
  if (!waitReply(messageSeq, 1000)) 
    printw("%s(): Failed to receive reply for QueryId %u (Timed out)!\n", 
           __FUNCTION__, messageSeq);     
  printw("%s(): Operation completed in %.2f ms\n", 
         __FUNCTION__, 1000.0*(ownTime_get_s()-begin));
}
//*****************************************************************************

static void test_Query(void)
{
  static char name[33] = "default";
  printw("Query for name [%s]: ", name);
  scanw("%s", name);
  halfdelay(1);

  const double begin = ownTime_get_s();

  TAPNameServiceFrame reg_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestIdByName,
                                    0x0000,
                                    ++messageSeq,
                                    "",
                                    0x00000000,
                                    0 };
  // Copy name from variable
  strcpy(reg_frame.name, name);
  
  *g_replynsframe = reg_frame;
  protocolDispSendPacket(&aplink,(void*)&g_replybuffer,sizeof(g_replybuffer));
  if (!waitReply(messageSeq, 1000)) 
    printw("%s(): Failed to receive reply for QueryId %u (Timed out)!\n", 
           __FUNCTION__, messageSeq);     

  printw("%s(): Operation completed in %.2f ms\n", 
         __FUNCTION__, 1000.0*(ownTime_get_s()-begin));
}
//*****************************************************************************

static void test_ReverseQuery(void)
{
  static uint32_t nodeid = 0x00000000;
  if (nodeid == 0x00) nodeid = localNodeId;
  printw("Query for NodeId [0x%08x]: ", nodeid);
  scanw("%x", &nodeid);
  halfdelay(1);

  const double begin = ownTime_get_s();

  TAPNameServiceFrame reg_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestNameById,
                                    0x0000,
                                    ++messageSeq,
                                    "",
                                    nodeid,
                                    0 };
  
  *g_replynsframe = reg_frame;
  protocolDispSendPacket(&aplink,(void*)&g_replybuffer,sizeof(g_replybuffer));
  if (!waitReply(messageSeq, 1000)) 
    printw("%s(): Failed to receive reply for ReverseQueryId %u (Timed out)!\n", 
           __FUNCTION__, messageSeq);     

  printw("%s(): Operation completed in %.2f ms\n", 
         __FUNCTION__, 1000.0*(ownTime_get_s()-begin)); 


}
//*****************************************************************************

static void test_Unregister(void)
{
  static char name[33] = "default";
  printw("Unregister name [%s]: ", name);
  scanw("%s", name);
  halfdelay(1);

  const double begin = ownTime_get_s();

  TAPNameServiceFrame reg_frame = { GIMNETAP_SERVICE_NAMESERVICE_ID,
                                    KAPNameServiceRequestUnregister,
                                    0x0000,
                                    ++messageSeq,
                                    "",
                                    0x00000000,
                                    0 };
  // Copy name from variable
  strcpy(reg_frame.name, name);
  
  *g_replynsframe = reg_frame;
  protocolDispSendPacket(&aplink,(void*)&g_replybuffer,sizeof(g_replybuffer));
  if (!waitReply(messageSeq, 1000)) 
    printw("%s(): Failed to receive reply for Unregister %u (Timed out)!\n", 
           __FUNCTION__, messageSeq);     

  printw("%s(): Operation completed in %.2f ms\n", 
         __FUNCTION__, 1000.0*(ownTime_get_s()-begin));

}
//*****************************************************************************

static int commandHandler(int value)
{
  int r = 1;
  printw("\n");
  switch(value) {
  
  case '1':
    testRun1();
    break;

  case 't':
    printw("TargetNodeId: ");
    if (scanw("%x", &targetNodeId) == 1) {
      g_replyfpframe->target_id = targetNodeId;
      printw("Using TargetNodeId: 0x%08x\n", targetNodeId); 

    } else {
      printw("Invalid TargetNodeId, must be a hex value\n");

    }
    halfdelay(1);
    
    break;

  case 'r':
    // Register
    test_Register();
    break;

  case 'e':
    // quEry
    test_Query();
    break;

  case 'i':
    // Query Id (reverseQuery)
    test_ReverseQuery();
    break;

  case 'u':
    // Unregister
    test_Unregister();
    break;

  case 'q':
    printw("Terminate program...\n");
    r = 0;
    break;

  case 'h':
    printw("\nAvailable Tests:\n");
    printw(" '1'       Static TestRun1\n");
    printw(" 't'       Set TargetNodeId\n");
    printw(" 'r'       Register name\n");
    printw(" 'e'       QuEry name\n");
    printw(" 'i'       Query Id (Reverse query)\n");
    printw(" 'u'       Unregister\n");
    printw(" 'q'       Quit\n");
    printw("\n");
    break;
  }
  return r;
}
//*****************************************************************************

static int ExecuteService(TProtocolDisp *link)
{
  int result = 0;

  // Init sync mechanisms
  lock = ownMutex_Init();
  cond = ownCond_Init();
  

  // Register callback,
  result = protocolDispRegisterCallback(link, FASTPROTOCOL_NUMBER, 
                                        fastProtocolCallback, NULL);
  result |= protocolDispRegisterCallback(link, HUBCTRLPROTOCOL_NUMBER, 
                                         hubctrlProtocolCallback, NULL);
  assert(result == 0);

  // Setup the default FastProtocol carrier frame
  memset(g_replyfpframe, 0x00, sizeof(TFastProtocolHeader));
  g_replyfpframe->source_id = 0x00;
  g_replyfpframe->target_id = targetNodeId;
  g_replyfpframe->flags = 0x00;
  g_replyfpframe->ttl = FASTPROTOCOL_DEFAULT_TTL;
  g_replyfpframe->statusvalue = 0x00;
  g_replyfpframe->protocol_id.protocol_number = FASTPROTOCOL_NUMBER;
  g_replyfpframe->protocol_id.total_size = sizeof(g_replybuffer);

  // Send reques for own id
  requestOwnId(link);

  // Init curses
  w = initscr(); 
  cbreak(); 
  //  noecho();
  nonl();
  intrflush(stdscr, FALSE);
  keypad(stdscr, TRUE);
  //nodelay(w, 1);
  halfdelay(1);
  atexit( (void (*)(void))endwin );

  int value;

  // Start processor
  protocolDispProcessorThreadHandle = ownThread_Create((void*)protocolDispProcessorThread, link);

  while (run) {
    showwait();
    
    // Read input
    value = getch();
    
    // Handle input
    if (value != ERR) {
      clear();
      refresh();

      run = commandHandler(value);
    }
  }
  return result;
}
//*****************************************************************************

static void showUsage()
{
}
//*****************************************************************************

static int SignalHandlerThread(void *t)
{
  dPrint(8, "Signal handler thread (%ld) starting...", pthread_self());

  while (run) {
    // Now, wait for requested signals to arrive
    int sig;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGPIPE);
    sigaddset(&mask, SIGHUP);
    sigwait(&mask, &sig);

    // Got signal, sigwait returned!
    dPrint(ODSTATUS,"Got Signal '%d' (%s)", sig, strsignal(sig));

    // What to do?
    switch(sig) {
    case SIGINT:
    case SIGTERM:
    case SIGPIPE:
    case SIGHUP:
      run = 0;
      ownThread_Signal(signalHandlerThread, SIGINT);
      ownSleep_ms(1000);
      break;
      
    default:
      dPrint(ODSTATUS,"Unhandled signal! Ignore!");
      break;
    }
  }
  return 0;
}
//*****************************************************************************

static void InitSignalHandler(void)
{
  // Now; Set to BLOCK ALL SIGNALS
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGTERM);
  sigaddset(&mask, SIGPIPE);
  sigaddset(&mask, SIGHUP);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);
  
  // Start signal handler thread
  signalHandlerThread = ownThread_Create((void*)SignalHandlerThread, NULL);
}
//*****************************************************************************

int main(int argc, char *argv[]) 
{
  int paramParseLoop = 1;
  //  unsigned int service_address = 0x00000001;
  //  const char *authenticationFile = NULL;
  char targetAddress[255];
  int targetTcpPort = 0; // Port to use
  int r = 0;

  
  // Set up utils (debugs mostly)
  debugInit();
  debugSetGlobalDebugLvl(ODINFO);
  debugSetLogFilename("nameservice_test.log");
  debugRemoveMode( DEBUG_MODE_TIMESTAMP_HH_MM_SS | DEBUG_MODE_TIMESTAMP_HH_MM_SS_MS );
  debugAddMode( DEBUG_MODE_TIMESTAMP_DELTA_SS_US );

  // Now; Set to BLOCK ALL SIGNALS
  InitSignalHandler();
  
  // Print version
  printf("\nGIMnet NameService TestApplication - %s\n\n", GIMNETAP_NAMESERVICE_TESTAPP_VERSIONSTRING);
  
  
  // Decode command line parameters.
  while(paramParseLoop) {
    int c;
    c = getopt(argc, argv, "u:p:qvh");
    switch(c) {
      
    case 'p':
      // Port to use
      targetTcpPort = atoi(optarg);
      break;
      
    case 'u':
      // Host to
      strncpy(targetAddress, optarg, sizeof(targetAddress)-1);
      targetAddress[sizeof(targetAddress)-1] = '\0';
      break;
      
      //////////////////////////////////
      //// GENERIC:                 ////
      //////////////////////////////////

    case 'v':
      // Increase only if not previously set to 0 (by 'quiet')
      if (verbose > 0) verbose++;
      debugSetGlobalDebugLvl(verbose);
      break;
      
    case 'q':
      verbose = 0;
      debugSetGlobalDebugLvl(verbose);
      debugSetLogFilename(NULL);
      break;
      
    case 'h':
    case '?':
      showUsage();
    exit(1);
    break;
    
    case -1:
      paramParseLoop = 0;
      break;
    }
  }

  // Now; got all parameters -> start processing
  // 1. Initialize TCP link
  int v = 0;
  v = protocolDispInit(&aplink);
  dPrint(ODTEST,"Connecting to '%s' at port %u", targetAddress, targetTcpPort);
  v &= protocolDispConnect(&aplink, targetAddress, targetTcpPort);
  if (v == PROTOCOLDISPATCHER_OK) {
    // OK!
    r = ExecuteService(&aplink);

  } else {
    // FAILED!
  } 

  printf("tcpHub exiting.\r\n");

  return r;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
